﻿using System;
using System.Data;
using System.Text;
using System.Linq;
using System.Collections.Generic;
using Microsoft.SharePoint.BusinessData.Administration;
using Microsoft.BusinessData.MetadataModel;
using Microsoft.BusinessData.Runtime;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.BusinessData.SharedService;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.Client;
using System.Reflection;
using System.IO;
using System.IO.Packaging;
using System.Xml;
using System.Security.Principal;
using System.Net;
using System.Web;
using System.Security;
using Microsoft.Office.SecureStoreService.Server;
using Microsoft.BusinessData.Infrastructure.SecureStore;

namespace TFSSPConnector
{
    public class CustomLobSystemStructure
    {
        public string LobSystem_SPSiteURL = "http://***";
        public string LobSystem_TFSServerURL = "http://***/tfs/DefaultCollection";
        public string LobSystem_Name = "TFSWITCustomConnector";
        public string LobSystem_TFSProjectName = "TeamProject";
        public SecureStoreCredentialCollection LobSystem_AppID = null;

        public string WorkItem_RequiredInForms = "System.Title";
        public string WorkItem_ShowInListView = "System.Id,System.Title,System.AssignedTo,System.State,System.WorkItemType";
        public string WorkItem_ShowInItemView = "";
        public string WorkItem_ReadOnlyFields = "Microsoft.VSTS.Common.StateChangeDate,Microsoft.VSTS.Common.ActivatedDate,Microsoft.VSTS.Common.ActivatedBy,Microsoft.VSTS.Common.ResolvedDate," +
            "Microsoft.VSTS.Common.ResolvedBy,Microsoft.VSTS.Common.ClosedDate,Microsoft.VSTS.Common.ClosedBy,System.CreatedDate,System.CreatedBy,System.ChangedBy," +
            "System.History";

        public string Attachment_ShowInListView = "WI.System.Id,System.Id,Name,CreationTime,AttachedTime,LastWriteTime,Uri,Comment";
        public string Attachment_ShowInItemView = "";
        public string Attachment_EntityName = "Attachments";

        public bool ValidateFields()
        {
            return (LobSystem_SPSiteURL != null && LobSystem_TFSServerURL != null && LobSystem_Name != null && LobSystem_TFSProjectName != null && WorkItem_RequiredInForms != null && WorkItem_ShowInListView != null && WorkItem_ShowInItemView != null && WorkItem_ReadOnlyFields != null && Attachment_ShowInListView != null && Attachment_ShowInItemView != null && Attachment_EntityName != null);
        }
    }
    
    public class CustomLobSystem
    {
        [Flags]
        enum wifieldflags : short
        {
            None = 0,
            Identifier = 1,
            ReadOnly = 2,
            ShowInListView = 4,
            ShowInItemView = 8,
            RequiredInForms = 16,
            Hyperlink = 32,
            All = 63
        }

        struct wifldstruct
        {
            public string displayName;
            public string systemType;
            public wifieldflags flags;
        }

        const string token = "123";
        const string Identifier = "System123Id";
        const string WIIdentifier = "WI123System123Id";
        const string WIWASLink = "WI123Hyperlink";

        static SPDiagnosticsService diagSvc;
        static string diagNamespace;
        static string diagWITType;

        public static byte[] ExportAllTFSWorkItemDefinitions(string TfsConnectionURL, SecureStoreCredentialCollection TfsUser, string TfsProject)
        {
            var wistore = ConnectToTFS(TfsConnectionURL, TfsUser);
            var prj = wistore.Projects[TfsProject];

            MemoryStream memstr = new MemoryStream();
            Package zip = Package.Open(memstr, FileMode.CreateNew);

            foreach (var TfsWorkItemType in prj.WorkItemTypes.Cast<WorkItemType>().Select(x => x.Name))
            {
                var witype = prj.WorkItemTypes[TfsWorkItemType];
                var xmldoc = witype.Export(false);

                Uri uri = PackUriHelper.CreatePartUri(new Uri(".\\"+TfsWorkItemType.Replace(' ','_')+".xml", UriKind.Relative));
                if (zip.PartExists(uri))
                {
                    zip.DeletePart(uri);
                }

                PackagePart part = zip.CreatePart(uri, System.Net.Mime.MediaTypeNames.Text.Xml, CompressionOption.Normal);
                using (Stream dest = part.GetStream())
                {
                    using (XmlWriter writer = XmlWriter.Create(dest))
                    {
                        xmldoc.WriteTo(writer); // Write to memorystream
                    }
                }
            }

            zip.Close();
            memstr.Position = 0;

            return memstr.ToArray();
        }

        public static bool ValidateTFSConnection(string TfsConnectionURL, SecureStoreCredentialCollection TfsUser, out string errormessage)
        {
            errormessage = null;
            WorkItemStore wistore = null;

            try
            {
                wistore = ConnectToTFS(TfsConnectionURL, TfsUser);
            }
            catch(Exception ex)
            {
                errormessage = ex.Message;
            }

            return (wistore != null ? true : false);
        }

        static void GetULSDiagnosticAreas()
        {
            string spSiteURL = "http://***";
            SPSite site = new SPSite(spSiteURL);
            SPDiagnosticsService diagnosticsService = site.WebApplication.Farm.Services.GetValue<SPDiagnosticsService>(String.Empty);
            //SPDiagnosticsService diagnosticsService = SPDiagnosticsService.Local;
            site.Dispose();
            
           
            foreach (SPDiagnosticsArea area in diagnosticsService.Areas)
            {
                Console.WriteLine("\nArea: {0}", area.Name);

                foreach (SPDiagnosticsCategory category in area.Categories)
                {
                    Console.WriteLine("Category: {0}", category.Name);
                }
            }
            Console.Write("\nPress ENTER to continue...");
            Console.ReadLine();
        }

        static void Log(string source, string message)
        {
            if (diagSvc != null)
            {
                SPDiagnosticsCategory cat = diagSvc.Areas["SharePoint Foundation"].Categories["Unknown"];
                var severity = (source.Contains("error") ? TraceSeverity.High : TraceSeverity.Medium);

                if (cat != null)
                    diagSvc.WriteTrace(0, cat, severity, "LobSystem: {0}, Entity: {1}, Source: {2}, Message: {3}", new object[] { diagNamespace, diagWITType, source, message });
            }
        }

        static Type GetNullableType(Type type)
        {
            if (type == null || type == typeof(void)) return null;
            if (!type.IsValueType || Nullable.GetUnderlyingType(type) != null) return type;

            return typeof(Nullable<>).MakeGenericType(type);
        }

        static string DecryptSecureString(SecureString secstr)
        {
            IntPtr ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(secstr);
            string sDecrypString = System.Runtime.InteropServices.Marshal.PtrToStringUni(ptr);

            return sDecrypString;
        }

        static WorkItemStore ConnectToTFS(string TFSConnectionURL, SecureStoreCredentialCollection TfsUser)
        {
            TfsTeamProjectCollection tfs;

            if (TfsUser == null)
            {
                tfs = new TfsTeamProjectCollection(new Uri(TFSConnectionURL));
            }
            else
            {
                string user = DecryptSecureString(TfsUser.First(x => x.CredentialType == SecureStoreCredentialType.WindowsUserName).Credential);
                string pass = DecryptSecureString(TfsUser.First(x => x.CredentialType == SecureStoreCredentialType.WindowsPassword).Credential);

                tfs = new TfsTeamProjectCollection(new Uri(TFSConnectionURL), new NetworkCredential(user, pass));
            }

                /*case 1:
                    if (HttpContext.Current == null) return null;
                    var baseUserTpcConnection = new TfsTeamProjectCollection(new Uri(TFSConnectionURL));
                    var ims = baseUserTpcConnection.GetService<IIdentityManagementService>();

                    var identity = ims.ReadIdentity(
                            IdentitySearchFactor.AccountName,
                            HttpContext.Current.User.Identity.Name,
                            MembershipQuery.None,
                            ReadIdentityOptions.None);

                    tfsCred = identity.Descriptor;
                    tfs = new TfsTeamProjectCollection(new Uri(TFSConnectionURL), tfsCred as IdentityDescriptor);
                    break;*/

            tfs.EnsureAuthenticated();
            
            var wistore = (WorkItemStore)tfs.GetService(typeof(WorkItemStore));

            //Log("ConnectToTFS", TFSConnectionURL);

            return wistore;
        }

        private static Dictionary<string, wifldstruct> GetWorkItemStructure(string RequiredInForms, string ShowInListView, string ShowInItemView, string ReadOnlyFields,
                                                                            WorkItemStore wistore, string witype, string TfsProject)
        {
            wifieldflags flag;
            var dict = new Dictionary<string, wifldstruct>();
            var prj = wistore.Projects[TfsProject];
            var witfields = prj.WorkItemTypes[witype].FieldDefinitions;
            RequiredInForms += ",";
            ShowInListView += ",";
            ShowInItemView += ",";
            ReadOnlyFields += ",";

            Log("GetWorkItemStructure", TfsProject + "-" + witype);

            flag = wifieldflags.None;
            if (ShowInListView.Length == 1 || ShowInListView.Contains(WIWASLink.Replace(token,".") + ",")) flag |= wifieldflags.ShowInListView;
            if (ShowInItemView.Length == 1 || ShowInItemView.Contains(WIWASLink.Replace(token,".") + ",")) flag |= wifieldflags.ShowInItemView;
            dict.Add(WIWASLink, new wifldstruct
            {
                displayName = "WorkItem Hyperlink",
                systemType = typeof(System.String).ToString(),
                flags = flag | wifieldflags.ReadOnly | wifieldflags.Hyperlink
            });

            foreach (var wit in witfields.Cast<FieldDefinition>().Where(x => x.Usage == FieldUsages.WorkItem && x.ReferenceName != "System.Id"))
            {
                flag = wifieldflags.None;

                if (!wit.IsEditable || wit.IsComputed) flag |= wifieldflags.ReadOnly;

                if (RequiredInForms.Contains(wit.ReferenceName + ","))
                {
                    flag |= wifieldflags.RequiredInForms;
                    Log("GetWorkItemStructure-RequiredInForms", wit.Name + "=" + wit.FieldType.ToString());
                }

                if (ShowInListView.Length == 1 || ShowInListView.Contains(wit.ReferenceName + ",")) flag |= wifieldflags.ShowInListView;
                if (ShowInItemView.Length == 1 || ShowInItemView.Contains(wit.ReferenceName + ",")) flag |= wifieldflags.ShowInItemView;
                if (ReadOnlyFields.Contains(wit.ReferenceName + ",")) flag |= wifieldflags.ReadOnly;

                dict.Add(wit.ReferenceName.Replace(".",token), new wifldstruct 
                    {   displayName = wit.Name,
                        systemType = GetNullableType(wit.SystemType).ToString(),
                        flags = flag 
                    });
                Log("GetWorkItemStructure-dict.Add", wit.Name + "=" + flag.ToString());
            }

            return dict;
        }

        private static Dictionary<string, wifldstruct> GetAttachmentStructure(string ShowInListView, string ShowInItemView)
        {
            wifieldflags flag;
            var dict = new Dictionary<string, wifldstruct>();
            ShowInListView += ",";
            ShowInItemView += ",";

            flag = wifieldflags.None;
            if (ShowInListView.Length == 1 || ShowInListView.Contains(WIIdentifier.Replace(token, ".") + ",")) flag |= wifieldflags.ShowInListView;
            if (ShowInItemView.Length == 1 || ShowInItemView.Contains(WIIdentifier.Replace(token, ".") + ",")) flag |= wifieldflags.ShowInItemView;
            dict.Add(WIIdentifier, new wifldstruct
            {
                displayName = "WorkItem ID",
                systemType = typeof(int).ToString(),
                flags = flag | wifieldflags.ReadOnly
            });

            flag = wifieldflags.None;
            if (ShowInListView.Length == 1 || ShowInListView.Contains(WIWASLink.Replace(token, ".") + ",")) flag |= wifieldflags.ShowInListView;
            if (ShowInItemView.Length == 1 || ShowInItemView.Contains(WIWASLink.Replace(token, ".") + ",")) flag |= wifieldflags.ShowInItemView;
            dict.Add(WIWASLink, new wifldstruct
            {
                displayName = "Attachment Hyperlink",
                systemType = typeof(System.String).ToString(),
                flags = flag | wifieldflags.ReadOnly | wifieldflags.Hyperlink
            });

            foreach(var propinfo in typeof(Attachment).GetProperties())
            {
                flag = wifieldflags.None;
                if (ShowInListView.Length == 1 || ShowInListView.Contains(propinfo.Name + ",")) flag |= wifieldflags.ShowInListView;
                if (ShowInItemView.Length == 1 || ShowInItemView.Contains(propinfo.Name + ",")) flag |= wifieldflags.ShowInItemView;

                var type = propinfo.PropertyType;
                
                if (type == typeof(System.Uri)) type = typeof(System.String);

                dict.Add(propinfo.Name, new wifldstruct 
                        {   displayName = propinfo.Name,
                            systemType = GetNullableType(type).ToString(),
                            flags = flag
                        });
            }

            return dict;
        }

        static string[] GetWorkItemTypes(WorkItemStore wistore, string TfsProject)
        {
            var prj = wistore.Projects[TfsProject];
            var res = prj.WorkItemTypes.Cast<WorkItemType>().Select(x => x.Name).ToArray();

            Log("GetWorkItemTypes", TfsProject + "-" + res.Length);

            return res;
        }

        public static bool CreateCustomLobSystem(CustomLobSystemStructure lobsysdata, AdministrationMetadataCatalog catalog, BdcServiceApplicationProxy proxy, SPDiagnosticsService diagnostics, out string errormessage)
        {
            Dictionary<string, wifldstruct> wistruct;
            string DefaultWIQL;
            string assemblyName;

            diagWITType = "global";
            diagNamespace = lobsysdata.LobSystem_Name;
            diagSvc = diagnostics;
            //site.WebApplication.Farm.Services.GetValue<SPDiagnosticsService>(String.Empty); //Farm.Local
            //SPDiagnosticsService diagSvc = SPDiagnosticsService.Local;

            //var catalog = AdministrationMetadataCatalog.GetCatalog(spSiteURL); 
            //-----------------get BCS and admin catalog--------------
            //SPSite site = new SPSite(lobsysdata.LobSystem_SPSiteURL);
            //BdcService service = site.WebApplication.Farm.Services.GetValue<BdcService>(String.Empty); //Farm.Local
            //SPServiceContext context = SPServiceContext.GetContext(site);   //SPServiceContext.Current;
            //var catalog = service.GetAdministrationMetadataCatalog(context);
            //site.Dispose();
            

            errormessage = null;

            //---------------delete existing BCS model------------------
            ModelCollection models = catalog.GetModels(lobsysdata.LobSystem_Name);
            bool hasFound = false;
            foreach (Model model in models)
            {
                Log("catalog.GetModels", "Deleting model '" + model.Name + "'...");
                try
                {
                    model.Delete();
                }
                catch (Exception ex)
                {
                    Log("catalog.GetModels error", ex.ToString());
                    errormessage = ex.ToString();
                    return false;
                }
                Log("catalog.GetModels", "Model deleted.");
                hasFound = true;
            }
            if (!hasFound)
            {
                Log("catalog.GetModels", "Model not found, no need to delete.");
            }

            //-------------make TFS connection--------------------------
            WorkItemStore wistore = ConnectToTFS(lobsysdata.LobSystem_TFSServerURL, lobsysdata.LobSystem_AppID);
            
            //----------------create BCS model and lobsystem-------------------
            Model bcsModel = Model.Create(lobsysdata.LobSystem_Name, true, catalog);
            LobSystem lobSystem = bcsModel.OwnedReferencedLobSystems.Create(lobsysdata.LobSystem_Name, true, SystemType.DotNetAssembly);
            //lobSystem.Properties.Add("SystemUtilityTypeName", typeof(TFSCustomConnector.TFSCustomConnector).AssemblyQualifiedName);
            //lobSystem.Properties.Add("SystemUtilityInstallDate", DateTime.Today.ToString());

            LobSystemInstance lobSystemInstance = lobSystem.LobSystemInstances.Create(lobsysdata.LobSystem_Name, true);
            lobSystemInstance.Properties.Add("TFSConnectionURL", lobsysdata.LobSystem_TFSServerURL);

            //---------------create attachments entity-----------------
            diagWITType = lobsysdata.Attachment_EntityName;
            DefaultWIQL = "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = '" + lobsysdata.LobSystem_TFSProjectName + "'";
            assemblyName = "TFSCustomConnector.AttachNETService"; //typeof(TFSCustomConnector.AttachNETService).FullName;

            wistruct = GetAttachmentStructure(lobsysdata.Attachment_ShowInListView, lobsysdata.Attachment_ShowInItemView);
            CreateEntity(false, typeof(string).ToString(), DefaultWIQL, wistruct, assemblyName, "", lobsysdata.Attachment_EntityName,
                            lobsysdata.LobSystem_Name, catalog, bcsModel, lobSystem, null, null, null, out errormessage);

            if (errormessage != null) return false;

            string assocIdentifierName = typeof(string).ToString();
            string assocEntityName = lobsysdata.Attachment_EntityName;
            var assocWIstruct = wistruct;

            //----------------create workitem entities------------
            assemblyName = "TFSCustomConnector.TFSNETService"; //typeof(TFSCustomConnector.TFSNETService).FullName;

            var wiType = "Bug";
            //foreach (var wiType in GetWorkItemTypes(wistore, lobsysdata.LobSystem_TFSProjectName))
            //{
                diagWITType = wiType;
                
                wistruct = GetWorkItemStructure(lobsysdata.WorkItem_RequiredInForms, lobsysdata.WorkItem_ShowInListView, lobsysdata.WorkItem_ShowInItemView,
                                                lobsysdata.WorkItem_ReadOnlyFields, wistore, wiType, lobsysdata.LobSystem_TFSProjectName);
                DefaultWIQL = "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = '" + lobsysdata.LobSystem_TFSProjectName +
                                "'  AND  [System.WorkItemType] = '" + wiType + "' ORDER BY [System.Id]";

                CreateEntity(true, typeof(int).ToString(), DefaultWIQL, wistruct, assemblyName, lobsysdata.LobSystem_TFSProjectName, wiType,
                                lobsysdata.LobSystem_Name, catalog, bcsModel, lobSystem, assocIdentifierName, assocEntityName, assocWIstruct, out errormessage);

                if (errormessage != null) return false;
            //}

            //-------------upload assembly to lobsystem----------------
            string assemblypath = typeof(TFSCustomConnector.TFSNETService).Assembly.Location;
            try
            {
                var bytes = File.ReadAllBytes(assemblypath);
                //lobSystem.PersistAssembly(bytes, null);
                PersistAssembly(bytes, catalog, proxy, lobSystem);
            }
            catch (Exception ex)
            {
                diagWITType = "global";
                errormessage = assemblypath + " => " + ex.ToString();
                Log("lobSystem.PersistAssembly error", errormessage);
                return false;
            }

            return true;
        }

        static byte[] ConvertIntToByteArray(int intValue)
        {
            byte[] buffer = new byte[4];
            for (int i = 0; i < 4; i++)
            {
                buffer[i] = (byte)((intValue >> (i * 8)) & 0xff);
            }
            return buffer;
        }

        static void PersistAssembly(byte[] buffer, AdministrationMetadataCatalog catalog, BdcServiceApplicationProxy proxy, LobSystem lobsystem)
        {

            List<byte> list = new List<byte>(buffer.Length);
            list.AddRange(ConvertIntToByteArray(buffer.Length));
            list.AddRange(buffer);
            buffer = list.ToArray();
            
            ProxyGenerationResult proxyGenerationResult = new ProxyGenerationResult
            {
                CompiledBytes = buffer
            };

            var catalogstruct = new AdministrationMetadataCatalogStruct(catalog);

            //proxy.SetLobSystemDataByLobSystemId(lobsystem.Id, proxyGenerationResult, catalogstruct.PartitionId);
            proxy.GetType().GetMethod("SetLobSystemDataByLobSystemId", BindingFlags.Instance | BindingFlags.NonPublic)
                .Invoke(proxy, new object[] { lobsystem.Id, proxyGenerationResult, catalogstruct.PartitionId });

            //proxy.InvalidateCacheNow();
            proxy.GetType().GetMethod("InvalidateCacheNow", BindingFlags.Instance | BindingFlags.NonPublic)
                .Invoke(proxy, null);
        }

        static Entity CreateEntity(bool isFullCRUD, string IdentifierType, string DefaultWIQL, Dictionary<string, wifldstruct> wistruct, 
                                    string assemblyName, string projectName, string entityName, string clsNamespace,
                                    AdministrationMetadataCatalog catalog, Model bcsModel, LobSystem lobSystem,
                                    string assocIdentifierType, string assocEntityName, Dictionary<string, wifldstruct> assocWIstruct,
                                    out string errormessage)
        {
            errormessage = null;
            //-----------------create entity----------------
            Entity witStateEntity = Entity.Create(entityName, clsNamespace, true, new Version("1.0.0.0"), 10000, CacheUsage.Default, lobSystem, bcsModel, catalog);
            witStateEntity.Properties.Add("TFSWorkItemType", entityName);
            witStateEntity.Properties.Add("TFSProjectName", projectName);
            witStateEntity.Properties.Add("Class", assemblyName + ", " + clsNamespace);
            witStateEntity.Properties.Add("DefaultWorkItemQuery", DefaultWIQL);
            witStateEntity.Identifiers.Create("ID", true, IdentifierType);

            //---------ReadItem----------------
            Method getItemMethod = witStateEntity.Methods.Create("ReadItem", true, true, "ReadItem");

            Parameter paramID = getItemMethod.Parameters.Create("ID", true, DirectionType.In);
            IdentifierReference identRef = new IdentifierReference("ID", new EntityReference(clsNamespace, entityName, catalog), catalog);
            paramID.CreateRootTypeDescriptor("ID", true, IdentifierType, "ID", identRef, null, TypeDescriptorFlags.Significant, null, catalog);

            Parameter returnParameter = getItemMethod.Parameters.Create("returnParameter", true, DirectionType.Return);
            TypeDescriptor returnTD = returnParameter.CreateRootTypeDescriptor("ReadItem", true, "Microsoft.BusinessData.Runtime.DynamicType", "ReadItem", null, null, TypeDescriptorFlags.Significant, null, catalog);
            CreateChildTypeDescriptors(returnTD, IdentifierType, clsNamespace, false, entityName, wistruct, TypeDescriptorFlags.None, catalog);

            getItemMethod.MethodInstances.Create("ReadItem", true, returnTD, MethodInstanceType.SpecificFinder, true);

            //---------ReadList----------------
            Method getListMethod = witStateEntity.Methods.Create("ReadList", true, true, "ReadList");

            Parameter filterParam = getListMethod.Parameters.Create("filter", true, DirectionType.In);
            FilterDescriptor filterDescr = getListMethod.FilterDescriptors.Create("filterDescr", true, FilterType.Wildcard, null);
            filterDescr.Properties.Add("IsDefault", true);
            filterDescr.Properties.Add("CaseSensitive", false);
            TypeDescriptor filterParamTD = filterParam.CreateRootTypeDescriptor("filterTD", true, typeof(string).ToString(), "filterTD", null, filterDescr, TypeDescriptorFlags.Significant, null, catalog);

            Parameter returnParameter2 = getListMethod.Parameters.Create("returnParameter", true, DirectionType.Return);
            TypeDescriptor returnCollTD2 = returnParameter2.CreateRootTypeDescriptor("ReadItemList", true, "Microsoft.BusinessData.Runtime.DynamicType[]", "ReadItemList", null, null, TypeDescriptorFlags.Significant | TypeDescriptorFlags.IsCollection, null, catalog);
            TypeDescriptor returnTD2 = returnCollTD2.ChildTypeDescriptors.Create("ReadItem", true, "Microsoft.BusinessData.Runtime.DynamicType", "ReadItem", null, null, TypeDescriptorFlags.Significant, null);
            CreateChildTypeDescriptors(returnTD2, IdentifierType, clsNamespace, true, entityName, wistruct, TypeDescriptorFlags.None, catalog);

            MethodInstance getListMethodInst = getListMethod.MethodInstances.Create("ReadList", true, returnCollTD2, MethodInstanceType.Finder, true);

            filterParamTD.SetDefaultValue(getListMethodInst.Id, DefaultWIQL);

            if (isFullCRUD)
            {
                //--------------Creator-------------
                Method getCreatorMethod = witStateEntity.Methods.Create("Create", true, true, "Create");

                Parameter createInputParameter = getCreatorMethod.Parameters.Create("newItem", true, DirectionType.In);
                TypeDescriptor createInputTD = createInputParameter.CreateRootTypeDescriptor("newItem", true, "Microsoft.BusinessData.Runtime.DynamicType", "newItem", null, null, TypeDescriptorFlags.Significant, null, catalog);
                CreateChildTypeDescriptors(createInputTD, IdentifierType, clsNamespace, false, entityName, wistruct, TypeDescriptorFlags.CreatorField, catalog);

                Parameter createReturnParameter = getCreatorMethod.Parameters.Create("returnParameter", true, DirectionType.Return);
                IdentifierReference identRef3 = new IdentifierReference("ID", new EntityReference(clsNamespace, entityName, catalog), catalog);
                TypeDescriptor createReturnTD = createReturnParameter.CreateRootTypeDescriptor("ID", true, IdentifierType, "ID", identRef3, null, TypeDescriptorFlags.Significant | TypeDescriptorFlags.ReadOnly, null, catalog);

                getCreatorMethod.MethodInstances.Create("Create", true, createReturnTD, MethodInstanceType.Creator, true);

                //--------------Updator-------------
                Method getUpdaterMethod = witStateEntity.Methods.Create("Update", true, true, "Update");

                Parameter updateInputNewParameter = getUpdaterMethod.Parameters.Create("updateNew", true, DirectionType.In);
                TypeDescriptor updateInputNewTD = updateInputNewParameter.CreateRootTypeDescriptor("updateNew", true, "Microsoft.BusinessData.Runtime.DynamicType", "updateNew", null, null, TypeDescriptorFlags.Significant, null, catalog);
                CreateChildTypeDescriptors(updateInputNewTD, IdentifierType, clsNamespace, false, entityName, wistruct, TypeDescriptorFlags.UpdaterField, catalog);

                getUpdaterMethod.MethodInstances.Create("Update", true, null, MethodInstanceType.Updater, true);

                //-----------AssociationNavigator-------------
                //AssociationGroup assocGroup = witStateEntity.AssociationGroups.Create("AssocGroup", true);
                //AssociationReference assocRef = assocGroup.AssociationReferences.Create("FindAssociations", new EntityReference(clsNamespace, entityName, catalog), false);
                AssociationReference assocRef = new AssociationReference("FindAssociations", new EntityReference(clsNamespace, entityName, catalog), catalog);
                
                Method findAssocMethod = witStateEntity.Methods.Create("FindAssociations", true, true, "FindAssociations");

                Parameter assocParamID = findAssocMethod.Parameters.Create("ID", true, DirectionType.In);
                IdentifierReference identRef6 = new IdentifierReference("ID", new EntityReference(clsNamespace, entityName, catalog), catalog);
                assocParamID.CreateRootTypeDescriptor("ID", true, IdentifierType, "ID", identRef6, null, TypeDescriptorFlags.Significant, assocRef, catalog);

                Parameter returnAssocParameter = findAssocMethod.Parameters.Create("returnAssocParameter", true, DirectionType.Return);
                TypeDescriptor returnCollTDAssoc = returnAssocParameter.CreateRootTypeDescriptor("AssocItemList", true, "Microsoft.BusinessData.Runtime.DynamicType[]", "AssocItemList", null, null, TypeDescriptorFlags.Significant | TypeDescriptorFlags.IsCollection, null, catalog);
                TypeDescriptor returnTDAssoc = returnCollTDAssoc.ChildTypeDescriptors.Create("AssocItem", true, "Microsoft.BusinessData.Runtime.DynamicType", "AssocItem", null, null, TypeDescriptorFlags.Significant, null);
                CreateChildTypeDescriptors(returnTDAssoc, assocIdentifierType, clsNamespace, true, assocEntityName, assocWIstruct, TypeDescriptorFlags.None, catalog);

                TypeDescriptor AttachID = returnTDAssoc.ChildTypeDescriptors.First(x => x.Name == WIIdentifier);
                AttachID.AssociationReference = assocRef;
                AttachID.IdentifierReference = new IdentifierReference("ID", new EntityReference(clsNamespace, entityName, catalog), catalog);
               
                MethodInstance findAssocInst = findAssocMethod.MethodInstances.Create("FindAssociations", true, returnCollTDAssoc, MethodInstanceType.AssociationNavigator,
                    new EntityReference(clsNamespace, assocEntityName, catalog),
                    new EntityReference[] { new EntityReference(clsNamespace, entityName, catalog) });

                string mappings = "&lt;?xml version=\"1.0\" encoding=\"utf-16\"?&gt;" +
                    "&lt;ForeignFieldMappings xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"&gt;&lt;ForeignFieldMappingsList&gt;" +
                    "&lt;ForeignFieldMapping ForeignIdentifierName=\"ID\" ForeignIdentifierEntityName=\"" + entityName + "\" ForeignIdentifierEntityNamespace=\"" + clsNamespace + "\" FieldName=\"" + WIIdentifier + "\" /&gt;" +
                    "&lt;/ForeignFieldMappingsList&gt;&lt;/ForeignFieldMappings&gt;";
                findAssocInst.Properties.Add("ForeignFieldMappings", mappings);
            }
            else
            {
                //---------StreamAccessor----------------
                Method getAccessorMethod = witStateEntity.Methods.Create("AccessItem", true, true, "AccessItem");

                Parameter paramACCID = getAccessorMethod.Parameters.Create("ID", true, DirectionType.In);
                IdentifierReference identRef5 = new IdentifierReference("ID", new EntityReference(clsNamespace, entityName, catalog), catalog);
                paramACCID.CreateRootTypeDescriptor("ID", true, IdentifierType, "ID", identRef5, null, TypeDescriptorFlags.Significant, null, catalog);

                Parameter returnACCParameter = getAccessorMethod.Parameters.Create("returnParameter", true, DirectionType.Return);
                TypeDescriptor returnACCTD = returnACCParameter.CreateRootTypeDescriptor("AccessItem", true, "System.IO.Stream", "AccessItem", null, null, TypeDescriptorFlags.Significant, null, catalog);

                MethodInstance metinstAccessor = getAccessorMethod.MethodInstances.Create("AccessItem", true, returnACCTD, MethodInstanceType.StreamAccessor, true);
                metinstAccessor.Properties.Add("FileNameField", "Name");
            }

            //-------------Deletor---------------
            Method getDeletorMethod = witStateEntity.Methods.Create("Delete", true, true, "Delete");

            Parameter deletorIDParameter = getDeletorMethod.Parameters.Create("ID", true, DirectionType.In);
            IdentifierReference identRef2 = new IdentifierReference("ID", new EntityReference(clsNamespace, entityName, catalog), catalog);
            deletorIDParameter.CreateRootTypeDescriptor("ID", true, IdentifierType, "ID", identRef2, null, TypeDescriptorFlags.Significant, null, catalog);

            getDeletorMethod.MethodInstances.Create("Delete", true, null, MethodInstanceType.Deleter, true);

            //-------------validate schema----------------
            ActivationError[] errors = witStateEntity.Validate();
            if (errors.Length == 0)
            {
                witStateEntity.Activate();
                Log("witStateEntity.Activate", "Model created");
            }
            else
            {
                Log("witStateEntity.Activate errors", "Validation errors:");
                Array.ForEach(errors, error => Log("witStateEntity.Activate errors", error.ToString()));

                var strBld = new StringBuilder();
                strBld.AppendLine("Failed to activate entity: " + entityName);
                Array.ForEach(errors, error => strBld.AppendLine(error.ToString()));
                errormessage = strBld.ToString();

                return null;
            }

            return witStateEntity;
        }

        static void CreateChildTypeDescriptors(TypeDescriptor parentDescr, string IdentifierType, string clsNamespace, bool isFinder, 
            string witype, Dictionary<string, wifldstruct> wistruct, TypeDescriptorFlags flag, AdministrationMetadataCatalog catalog)
        {
            TypeDescriptor childDescr;
            string fieldName, fieldType;
            TypeDescriptorFlags innerflag;

            if (flag == TypeDescriptorFlags.CreatorField)
            {
                childDescr = parentDescr.ChildTypeDescriptors.Create(Identifier, true, IdentifierType, Identifier, null, null, TypeDescriptorFlags.None, null);
                childDescr.DefaultDisplayName = "ID";
            }
            else if (flag == TypeDescriptorFlags.UpdaterField)
            {
                IdentifierReference identRef = new IdentifierReference("ID", new EntityReference(clsNamespace, witype, catalog), catalog);
                childDescr = parentDescr.ChildTypeDescriptors.Create(Identifier, true, IdentifierType, Identifier, identRef, null, TypeDescriptorFlags.Significant | TypeDescriptorFlags.PreUpdaterField | TypeDescriptorFlags.UpdaterField, null);
                childDescr.DefaultDisplayName = "ID";
            }
            else
            {
                IdentifierReference identRef = new IdentifierReference("ID", new EntityReference(clsNamespace, witype, catalog), catalog);
                childDescr = parentDescr.ChildTypeDescriptors.Create(Identifier, true, IdentifierType, Identifier, identRef, null, flag | TypeDescriptorFlags.ReadOnly | TypeDescriptorFlags.Significant, null);
                childDescr.DefaultDisplayName = "ID";
            }

            foreach (var widescr in wistruct)
            {
                fieldName = widescr.Key;
                fieldType = widescr.Value.systemType;
                innerflag = flag;

                if (isFinder && (widescr.Value.flags & wifieldflags.ShowInListView) == wifieldflags.ShowInListView ||
                    !isFinder && (widescr.Value.flags & wifieldflags.ShowInItemView) == wifieldflags.ShowInItemView)
                {
                    if (flag == TypeDescriptorFlags.UpdaterField && (widescr.Value.flags & wifieldflags.ReadOnly) != wifieldflags.ReadOnly ||
                        flag == TypeDescriptorFlags.CreatorField && (widescr.Value.flags & wifieldflags.RequiredInForms) == wifieldflags.RequiredInForms ||
                        flag != TypeDescriptorFlags.UpdaterField && flag != TypeDescriptorFlags.CreatorField)
                    {
                        if ((widescr.Value.flags & wifieldflags.ReadOnly) == wifieldflags.ReadOnly)
                        {
                           innerflag |= TypeDescriptorFlags.ReadOnly;
                        }

                        childDescr = parentDescr.ChildTypeDescriptors.Create(fieldName, true, fieldType, fieldName, null, null, TypeDescriptorFlags.Significant | innerflag, null);
                        childDescr.DefaultDisplayName = widescr.Value.displayName;
                        
                        if (!isFinder && (widescr.Value.flags & wifieldflags.RequiredInForms) == wifieldflags.RequiredInForms)
                        {
                            childDescr.Properties.Add("RequiredInForms", true);
                        }
                        if ((widescr.Value.flags & wifieldflags.Hyperlink) == wifieldflags.Hyperlink)
                        {
                            childDescr.Properties.Add("SPCustomFieldType", "URL");
                        }

                        Log("CreateChildTypeDescriptors", widescr.Key + "=" + flag.ToString() + "," + widescr.Value.flags.ToString());
                    }
                }
            }
        }
    }
}

/*
 <Method IsStatic="true" Name="FindAssociations">
<Parameters>
<Parameter Direction="In" Name="ID">
  <TypeDescriptor TypeName="System.Int32" 
 * IdentifierName="ID" 
 * IdentifierEntityName="Bug" 
 * IdentifierEntityNamespace="TFSWITCustomConnector" 
 * ForeignIdentifierAssociationName="FindAssociations" Name="ID" />
</Parameter>
<Parameter Direction="Return" Name="FindAssociations">
  <TypeDescriptor TypeName="Microsoft.BusinessData.Runtime.DynamicType[]" IsCollection="true" Name="FindAssociations">
    <TypeDescriptors>
      <TypeDescriptor TypeName="Microsoft.BusinessData.Runtime.DynamicType" Name="FindAssociationsElement">
        <TypeDescriptors>
          ...
        </TypeDescriptors>
      </TypeDescriptor>
    </TypeDescriptors>
  </TypeDescriptor>
</Parameter>
</Parameters>
<MethodInstances>
<Association Name="FindAssociations" Type="AssociationNavigator" ReturnParameterName="FindAssociations" DefaultDisplayName="Attachments Navigate Association">
  <Properties>
    <Property Name="ForeignFieldMappings" Type="System.String">
&lt;?xml version="1.0" encoding="utf-16"?&gt;
&lt;ForeignFieldMappings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
&lt;ForeignFieldMappingsList&gt;
&lt;ForeignFieldMapping ForeignIdentifierName="ID" ForeignIdentifierEntityName="Bug" ForeignIdentifierEntityNamespace="TFSWITCustomConnector" FieldName="WI123System123Id" /&gt;
&lt;/ForeignFieldMappingsList&gt;
&lt;/ForeignFieldMappings&gt;
 </Property>
  </Properties>
  <SourceEntity Namespace="TFSWITCustomConnector" Name="Bug" />
  <DestinationEntity Namespace="TFSWITCustomConnector" Name="Attachments" />
</Association>
</MethodInstances>
</Method>
</Methods>
 */